#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <assert.h>
#include <arpa/inet.h>



#define SERVER_DOMAIN_NAME          ("www.machtalk.net")
#define SERVER_DEFAULT_PORT         (7779)       // 登录时使用，正式通信时是服务器重新分配的
#define MAXDATASIZE                 (1024)
#define TMP_BUFF_SIZE               (64)
#define IP_MAX_SIZE                 (32)
#define PORT_MAX_SIZE               (10)
#define DEVICE_ID_MAX_SIZE          (64)
#define DEVICE_PIN_MD5_MAX_SIZE     (64)

// 需要根据自己的实际情况修改
// md5加密的在线工具 http://www.bejson.com/enc/md5/
#define DEVICE_ID                   ("4415cfa2e6ce46a78f331a3ced034927")        // 设备ID
#define DEVICE_PIN_MD5              ("76ec8ee0de1c49bf373e3bbda3fb1482")        // md5加密后的设备PIN
#define HTSENSOR_ATTRIBUTE_ID       (1)         // 温湿度传感器的属性值ID
#define LED_ATTRIBUTE_ID            (2)         // LED的属性值ID
//#define DEVICE_ID                   ("552cfd991f714c2bbef6b923be4a8543")        // 设备ID
//#define DEVICE_PIN_MD5              ("1493e8729227186d9d43fb59b2e58afb")        // md5加密后的设备PIN


typedef enum 
{
    false = 0,
    true  = !false,
}bool;


char g_deviceID[DEVICE_ID_MAX_SIZE];                // 设备ID
char g_devicePinMd5[DEVICE_PIN_MD5_MAX_SIZE];       // md5加密的设备Pin


float GetCurrentTemp(void);


// 获取当前led的状态
// @ret 当前led的状态
int GetCurrentLedStatus(void)
{
    return 1;
}


// 获取消息的序列号
long GetMid(void)
{
    static long mid = 0;

    ++mid;
    return mid;
}

// 登录，从服务器获取分配的IP和端口
// @pstIp 服务器重新分配的IP
// @pstPort 服务器重新分配的Port
int GetServerIpAndPort(char *pstIp, 
                       char *pstPort)
{
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE] = {0}; /*./client 127.0.0.1 hello */
  char snd_buf[MAXDATASIZE] = {0};
  struct hostent *host;             /* struct hostent
                                     * {
                                     * char *h_name; // general hostname
                                     * char **h_aliases; // hostname's alias
                                     * int h_addrtype; // AF_INET
                                     * int h_length; 
                                     * char **h_addr_list;
                                     * };
                                     */
  struct sockaddr_in server_addr;
    char *pstTmp = NULL;

    // 组装'登录'命令的报文
    sprintf(snd_buf, 
            "{\"cmd\":\"login\",\"mid\":\"%ld\",\"type\":2,\"usr\":\"%s\",\"pwd\":\"%s\"}\r\n",
            GetMid(),
            g_deviceID,
            g_devicePinMd5);

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(1);
  }

  host = gethostbyname(SERVER_DOMAIN_NAME);
  if (NULL==host)
  {
    fprintf(stderr, "[%s]: gethostbyname() fail.\r\n", __FUNCTION__);
    exit(1);
  }
  bzero(&server_addr,sizeof(struct sockaddr_in));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVER_DEFAULT_PORT);
  server_addr.sin_addr = *(struct in_addr *)(host->h_addr_list[0]);


  /* create the connection by socket 
   * means that connect "sockfd" to "server_addr"
   * 同步阻塞模式 
   */
  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
    perror("connect");
    exit(1);
  }

  /* 同步阻塞模式  */
  if (send(sockfd, snd_buf, strlen(snd_buf), 0) == -1)
  {
    perror("send:");
    exit(1);
  }
  printf("[%s]: send:%s\n", __FUNCTION__, snd_buf);

   /* 同步阻塞模式  */
  if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
  {
    perror("recv:");
    exit(1);
  }

  rcv_buf[recvbytes] = '\0';
  printf("[%s]: recv:%s\n", __FUNCTION__, rcv_buf);

  // 取服务器分配的ip和端口
  if (NULL == (pstTmp=strstr(rcv_buf, "\"ip\":\"")))          // 解析IP
  {
    perror("strstr IP: ");
    exit(1);
  }
  if (-1 == sscanf(pstTmp, "\"ip\":\"%[^\"]", pstIp))
  {
    perror("sscanf IP: ");
    exit(1);
  }
  if (NULL == (pstTmp=strstr(rcv_buf, "\"port\":\"")))         // 解析Port
  {
    perror("sscanf Port: ");
    exit(1);
  }
  if (-1 == sscanf(pstTmp, "\"port\":\"%[^\"]", pstPort))
  {
    perror("sscanf Port: ");
    exit(1);
  }
  printf("[%s]: server allocation IP=%s, Port=%s\r\n", __FUNCTION__, pstIp, pstPort);

  close(sockfd);
  return 0;
}


// 连接确认
// @cpcIp 服务器IP
// @cpcPort 服务器端口
// @ret 与服务器通信的socket
int confirmconn(const char *cpcIp, const char *cpcPort)
{
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE] = {0}; /*./client 127.0.0.1 hello */
  char snd_buf[MAXDATASIZE] = {0};
  struct sockaddr_in server_addr;

    // 组装'连接确认'命令的报文
    sprintf(snd_buf, 
            "{\"cmd\":\"connect\",\"mid\":\"%ld\",\"usr\":\"%s\",\"type\":2,\"encode\":\"\"}\r\n",
            GetMid(),
            g_deviceID);

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
  {
    perror("socket:");
    exit(-1);
  }
  
  bzero(&server_addr,sizeof(struct sockaddr_in));
  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(atoi(cpcPort));
  server_addr.sin_addr.s_addr= inet_addr(cpcIp);


  /* create the connection by socket 
   * means that connect "sockfd" to "server_addr"
   * 同步阻塞模式 
   */
  if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
  {
    perror("connect");
    exit(-1);
  }

  /* 同步阻塞模式  */
  if (send(sockfd, snd_buf, strlen(snd_buf), 0) == -1)
  {
    perror("send:");
    exit(-1);
  }
  printf("[%s]: send:%s\n", __FUNCTION__, snd_buf);

   /* 同步阻塞模式  */
  if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1)
  {
    perror("recv:");
    exit(-1);
  }

  rcv_buf[recvbytes] = '\0';
  printf("[%s]: recv:%s\n", __FUNCTION__, rcv_buf);

  return sockfd;
}

// 接收并解析服务器的命令
// @sockfd 与服务器通信的socket
// @pcRecvBuff 接收缓存
// @ret 接收到的数据长度
int Recv(int sockfd, char *pcRecvBuff)
{
    fd_set recvFdSet;
    int maxfd = 0;
    struct timeval timeout = {0, 0};
    int recvBytes = 0;

    FD_ZERO(&recvFdSet);
    FD_SET(sockfd, &recvFdSet);
    if (maxfd < sockfd)
    {
        maxfd = sockfd+1;
    }

    switch (select(maxfd, &recvFdSet, NULL, NULL, &timeout))
    {
        case -1:        // 出错
        {
            printf("[%s]: select error.\r\n", __FUNCTION__);
            return -1;
        }

        case 0:         // 暂时没有需要接收的
        {
            break;
        }

        default:        // 有数据要接收
        {
            if (FD_ISSET(sockfd, &recvFdSet))       // 确认是否网络上有数据待接收
            {
                // 接收数据
                if (-1 == (recvBytes=recv(sockfd, pcRecvBuff, MAXDATASIZE, 0)))
                {
                    printf("[%s]: recv error.\r\n", __FUNCTION__);
                    return -1;
                }
                printf("[%s]: recv %d bytes\r\n%s\r\n", __FUNCTION__, recvBytes, pcRecvBuff);

                return recvBytes;
            }
        }
    }

    return 0;
}

// 判断是否为制定命令
// @pcBuff 命令缓存
// @buffBytes 缓存中有效数据的字节数
// @requiredMinBytes 制定命令的最小长度
// @cpcRequiredString 制定命令中必须包含的字符串
// @ret true or false
bool IsSpecifiedCmd(char *pcBuff, 
                    int buffBytes, 
                    int requiredMinBytes, 
                    const char *cpcRequiredString)
{
    // 判断长度是否满足要求
    if (buffBytes < requiredMinBytes)
    {
        return false;
    }

    // 判断是否包含制定字符串
    if (NULL == strstr(pcBuff, cpcRequiredString))
    {
        return false;
    }

    return true;
}


// 判断是否是服务器下发的心跳请求
// @pcBuff 接收命令的缓存
// @bytes 缓存中有效数据的字节数
// @ret true or false
bool IsHeartBeatCmdReq(char *pcBuff, int bytes)
{
    assert(NULL!=pcBuff);
    
    char heartBeatReqCmd[] = {"{0}\r\n"};

    return IsSpecifiedCmd(pcBuff, bytes, strlen(heartBeatReqCmd), heartBeatReqCmd);
}


// 判断是否是服务器下发的操作led的请求
// @pcBuff 接收命令的缓存
// @buffBytes 缓存中有效数据的字节数
// @ledAttributeID LED的属性值ID
// @ret true or false
bool IsOptLedReq(char *pcBuff, int buffBytes, int ledAttributeID)
{
    char optLedReqCmd[] = {"{\"cmd\":\"opt\",\"dvid\":2,\"from\":\"caogos\",\"mid\":\"287\",\"to\":\"4415cfa2e6ce46a78f331a3ced034927\",\"value\":\"1\"}\r\n"};
    int requiredMinBytes = 0; 
    char tmpBuff[TMP_BUFF_SIZE] = {0};
    
    assert(NULL!=pcBuff);

    // 计算请求报文最小长度
    // 根据一个示例报文大概计算一个长度
    // 由于这是根据示例报文算的，真实的报文长度可能会有一些偏差，所以这里减去示例报文中用户名的长度
    requiredMinBytes = strlen(optLedReqCmd)-strlen("caogos");

    // 生成用于判定是否为操作led命令的特征字符串
    sprintf(tmpBuff, "{\"cmd\":\"opt\",\"dvid\":%d,", ledAttributeID);

    return IsSpecifiedCmd(pcBuff, buffBytes, requiredMinBytes, tmpBuff);
}


// 查询设备状态请求
// @pcBuff 命令缓存
// @buffBytes 缓存中的有效数据的字节数
// @ret true or false
bool IsQueryDeviceStatusReq(char *pcBuff, int buffBytes)
{
    char queryDeviceStatusReqCmd[] = {"{\"cmd\":\"query\",\"from\":\"caogos\",\"mid\":\"199\",\"to\":\"4415cfa2e6ce46a78f331a3ced034927\"}"};
    int requiredMinBytes = 0;
    char tmpBuff[TMP_BUFF_SIZE] = {0};
    
    assert(NULL!=pcBuff);

    // 计算请求报文的最小长度
    // 减去示例报文中的用户名部分，因为每个人的用户名长度可能不同
    requiredMinBytes = strlen(queryDeviceStatusReqCmd)-strlen("caogos");

    // 生成用于判定是否为查询设备状态请求的特征字符串
    strcpy(tmpBuff, "{\"cmd\":\"query\",\"from\":");

    return IsSpecifiedCmd(pcBuff, buffBytes, requiredMinBytes, tmpBuff);
}


// 发送心跳响应
// @sockfd 与服务器通信的socket
void SendHeartBeatRsp(int sockfd)
{
    char sendBuff[] = {"{1}\r\n"};

    if (-1 == send(sockfd, sendBuff, strlen(sendBuff), 0))
    {
        printf("[%s]: send error.\r\n", __FUNCTION__);
        return ;
    }
    printf("[%s]: send: %s\r\n", __FUNCTION__, sendBuff);

    return ;
}

// 发送操作led的响应
// @sockfd 与服务器通信的socket
// @cpcToString 响应中字段"to"的值
// @pcMidString 字符串形式的mid值
// @ledAttributeID LED的属性值ID
// @ledValue LED的值
void SendOptLedRsp(const int sockfd, const char *cpcToString, char *pcMidString, const int ledAttributeID, const int ledValue)
{
    char sendBuff[MAXDATASIZE] = {0};

    sprintf(sendBuff, 
            "{\"cmd\":\"post\",\"to\":\"%s\",\"values\":[{\"dvid\":%d,\"value\":\"%d\"}],\"needack\":1,\"mid\":\"%s\"}\r\n",
            cpcToString,
            ledAttributeID,
            ledValue,
            pcMidString);
    printf("[%s]: send:%s\r\n", __FUNCTION__, sendBuff);
    if (-1 == send(sockfd, sendBuff, strlen(sendBuff), 0))
    {
        printf("[%s]: send fail.\r\n", __FUNCTION__);
        return ;
    }

    return ;
}


// 发送查询设备状态的响应
// @cpcRecvBuff 接收命令缓存
// @sockfd 与服务器通信的socket
void SendQueryDeviceStatusRsp(const char *cpcRecvBuff, const int sockfd)
{
    char sendBuff[MAXDATASIZE] = {0};           // 发送缓存
    char *pcCmdStartPosition = NULL;            // 指向命令开始的地方
    char *pcMidPosition = NULL;                 // 指向命令的"mid"
    char midString[TMP_BUFF_SIZE] = {0};        // 字符串形式的mid值
    char *pcFrom = NULL;                        // 指向请求命令中的"from"
    char fromString[TMP_BUFF_SIZE] = {0};       // 字符串形式的from值
    float htsensorValue = 0;                    // 温湿度值
    int ledValue = 0;                           // led的状态

    // 取mid值
    if (NULL == (pcCmdStartPosition=strstr(cpcRecvBuff, "{\"cmd\":\"query\",")))
    {
        printf("[%s]: not find QueryDeviceStatusReq cmd.\r\n", __FUNCTION__);
        return ;
    }
    if (NULL == (pcMidPosition=strstr(pcCmdStartPosition, "\"mid\":")))
    {
        printf("[%s]: not find mid.\r\n", __FUNCTION__);
        return ;
    }
    if (-1 == sscanf(pcMidPosition, "\"mid\":\"%[^\"]", midString))
    {
        printf("[%s]: sscanf mid fail.\r\n", __FUNCTION__);
        return ;
    }
    printf("[%s]: mid=%s\r\n", __FUNCTION__, midString);

    // 请求命令发送者，将其作为响应的目标者
    if (NULL == (pcFrom=strstr(pcCmdStartPosition, "\"from\":")))
    {
        printf("[%s]: not find from.\r\n", __FUNCTION__);
        return ;
    }
    if (-1 == sscanf(pcFrom, "\"from\":\"%[^\"]", fromString))
    {
        printf("[%s]: sscanf from fail.\r\n", __FUNCTION__);
        return ;
    }
    printf("[%s]: from=%s\r\n", __FUNCTION__, fromString);

    // 组报文
    htsensorValue = GetCurrentTemp();
    ledValue = GetCurrentLedStatus();
    sprintf(sendBuff,
            "{\"mid\":\"%s\",\"cmd\":\"resp\",\"to\":\"%s\",\"success\":1,\"values\":[{\"dvid\":1,\"value\":\"%f\"},{\"dvid\":2,\"value\":\"%d\"}]}\r\n",
            midString,
            fromString,
            htsensorValue,
            ledValue);

    // 发送
    if (-1 == send(sockfd, sendBuff, strlen(sendBuff), 0))
    {
        printf("[%s]: send fail. rsp=%s\r\n", __FUNCTION__, sendBuff);
        return ;
    }
    printf("[%s]: send rsp=%s\r\n", __FUNCTION__, sendBuff);

    return ;
}


// 操作LED
// @cpcBuff 命令缓存
// @ledAttributeID LED的属性值ID
// @sockfd 与服务器通信的socket
void OptLed(const char *cpcBuff, const int ledAttributeID, const int sockfd)
{
    int ledValue = 0;
    char ledValueString[TMP_BUFF_SIZE] = {0};   // 字符串形式的led值
    char midString[TMP_BUFF_SIZE] = {0};        // 字符串形式的mid值
    char fromString[TMP_BUFF_SIZE] = {0};       // 字符串形式的from值
    char tmpBuff[TMP_BUFF_SIZE] = {0};
    char *pcCmdStartPosition = NULL;            // 指向命令开始的地方
    char *pcLedValuePosition = NULL;            // 指向命令中"value"
    char *pcMidPosition = NULL;                 // 指向命令的"mid"
    char *pcFrom = NULL;                        // 指向命令中的"from"

    assert(NULL!=cpcBuff);

    // 取led的值
    sprintf(tmpBuff, "{\"cmd\":\"opt\",\"dvid\":%d,", ledAttributeID);
    if (NULL == (pcCmdStartPosition=strstr(cpcBuff, tmpBuff)))
    {
        printf("[%s]: not find opt led cmd.\r\n", __FUNCTION__);
        return ;
    }
//    printf("[%s]: pcCmdStart=%s, tmpBuff=%s\r\n", __FUNCTION__, pcCmdStartPosition, tmpBuff);
    if (NULL == (pcLedValuePosition=strstr(pcCmdStartPosition, "\"value\":")))
    {
        printf("[%s]: not find value.\r\n", __FUNCTION__);
        return ;
    }
//    printf("[%s]: pcLedValue=%s\r\n", __FUNCTION__, pcLedValuePosition);
    if (-1 == sscanf(pcLedValuePosition, "\"value\":\"%[^\"]", ledValueString))
    {
        printf("[%s]: sscanf led value fail.\r\n", __FUNCTION__);
        return ;
    }
    ledValue = atoi(ledValueString);
    printf("[%s]: ledValue=%d\r\n", __FUNCTION__, ledValue);

    // 取mid
    if (NULL == (pcMidPosition=strstr(pcCmdStartPosition, "\"mid\":")))
    {
        printf("[%s]: not find mid.\r\n", __FUNCTION__);
        return ;
    }
//    printf("[%s]: pcMidPosition=%s\r\n", __FUNCTION__, pcMidPosition);
    if (-1 == sscanf(pcMidPosition, "\"mid\":\"%[^\"]", midString))
    {
        printf("[%s]: sscanf mid value fail.\r\n", __FUNCTION__);
        return ;
    }
    printf("[%s]: midString=%s\r\n", __FUNCTION__, midString);

    // 取from
    if (NULL == (pcFrom=strstr(pcCmdStartPosition, "\"from\":")))
    {
        printf("[%s]: not find from.\r\n", __FUNCTION__);
        return ;
    }
    if (-1 == sscanf(pcFrom, "\"from\":\"%[^\"]", fromString))
    {
        printf("[%s]: sscanf from value fail.\r\n", __FUNCTION__);
        return ;
    }
    printf("[%s]: fromString=%s\r\n", __FUNCTION__, fromString);
    
    // 发送响应
    SendOptLedRsp(sockfd, fromString, midString, ledAttributeID, ledValue);

    return ;
}


// 解析服务器发送的内容
// @pcBuff 接收到的数据
// @bytes 接收到的字节数
// @sockfd 与服务器通信的socket
void Parse(char *pcBuff, int bytes, int sockfd)
{
    // 心跳请求
    if (true == IsHeartBeatCmdReq(pcBuff, bytes))
    {
        // 发送心跳响应
        SendHeartBeatRsp(sockfd);
    }

    // 操作LED的请求
    if (true == IsOptLedReq(pcBuff, bytes, LED_ATTRIBUTE_ID))
    {
        // 执行动作
        OptLed(pcBuff, LED_ATTRIBUTE_ID, sockfd);
    }

    // 查询设备状态的请求
    if (true == IsQueryDeviceStatusReq(pcBuff, bytes))
    {
        // 响应
        SendQueryDeviceStatusRsp(pcBuff, sockfd);
    }

    return ;
}


// 接收并解析服务器的命令
// @sockfd 与服务器通信的socket
void RecvAndParse(int sockfd)
{
    char recvBuff[MAXDATASIZE] = {0};
    int recvBytes = 0;

    // 接收
    if (0 >= (recvBytes=Recv(sockfd, recvBuff)))
    {
        // 没有收到数据，则直接返回
        return ;
    }
    
    // 解析
    Parse(recvBuff, recvBytes, sockfd);

    return ;
}


// 获取当前温度值
// @ret 当前温度值
float GetCurrentTemp(void)
{
    int fd = 0;
    char tempBuff[TMP_BUFF_SIZE] = {0};
    int tmp = 0;
    float fTemperature = 0;

    // 读温度值
    if (-1 == (fd=open("/sys/devices/platform/dht11.0/temp1_input", O_RDONLY)))
    {
        printf("%s: open dht11 file.\r\n", __FUNCTION__);
        exit(1);
    }
    if (0 >= read(fd, tempBuff, TMP_BUFF_SIZE))
    {
        printf("%s: read dht11 temperature fail.\r\n", __FUNCTION__);
        exit(1);
    }
    close(fd);

    // 将读到的温度值转换为浮点数
    // 第一个字节为温度的整数，第二个字节为温度的小数
    tmp = atoi(tempBuff);
    fTemperature = ((tmp>>8)&0xff) + ((tmp&0xff)*1.0)/0xff;
    printf("%s: DHT11 temperature is %f\r\n", __FUNCTION__, fTemperature);

    return fTemperature;
}


// 采集温湿度并上报服务器
// @sockfd 与服务器通信的socket
void CollectAndSend(int sockfd)
{
	float value=0;
	char snd_wendu[MAXDATASIZE] = {0};
    int htsensorAttributeID = 0;

    // 组报文
    value = GetCurrentTemp();       // 获取当前温度值
    htsensorAttributeID = HTSENSOR_ATTRIBUTE_ID;
    sprintf(snd_wendu, 
            "{\"cmd\":\"post\",\"values\":[{\"dvid\":%d,\"value\":\"%f\"}],\"needack\":1,\"mid\":\"%ld\"}\r\n",
            htsensorAttributeID,
            value,
            GetMid());

    // 发送
    if (send(sockfd, snd_wendu, strlen(snd_wendu), 0) == -1)
     {
        perror("send:");
        exit(-1);
     }
    printf("[%s]: send:%s\n", __FUNCTION__, snd_wendu);

    return ;
}


int main(void)
{
    char ip[IP_MAX_SIZE] = {0};                 // 服务器分配的IP
    char port[PORT_MAX_SIZE] = {0};             // 服务器分配的端口
    int sockfd = 0;

    // 初始化设备信息
    strcpy(g_deviceID, DEVICE_ID);
    strcpy(g_devicePinMd5, DEVICE_PIN_MD5);

    // 登录，从服务器获取分配的IP和端口
    GetServerIpAndPort(ip, port);
    printf("[%s]: Login Success. new server IP=%s, Port=%s\r\n", __FUNCTION__, ip, port);

    // 连接服务器新端口(协议文档中的'连接确认'命令)
	if (-1 == (sockfd=confirmconn(ip, port)))
	{
        printf("[%s]: Confirm Connection Fail.\r\n", __FUNCTION__);
        return -1;
	}
	
    while (1)
    {
        // 发送cpu占有率
        CollectAndSend(sockfd);

        // 接收并解析服务器的命令
        RecvAndParse(sockfd);

        // 延时
        sleep(1);
    }
    
    return 0;
}



